#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#define QUIT_STRING "exit" //종료문자 
#define FFLAG (O_WRONLY | O_CREAT | O_TRUNC) //리다이렉션의 > 연산에 사용
#define FFLAG_ADD (O_WRONLY | O_APPEND) //리다이렉션의 >>연산에서 사용
#define FMODE (S_IRUSR | S_IWUSR) //리다이렉션시 파일권한 설정 
#define MAX_STRING 128 //가장큰 스트링값 


void executecmd(char *incmd); //명령어의 실행을 관장한다 
void executeredirect(char *s, int in, int out); //리다이렉션을 처리한다 
int makeargv(const char *s, const char *delimiters, char ***argvp); //인자값을 처리한다 
int parseandredirectin(char *s); //<의 리다이렉션을 처리 
int parseandredirectout(char *s); //>의 리다이렉션을 처리 
int parseandredirectadd(char *s); //>>의 리다이렉션을 처리 
static void perror_exit(char *s); //에러처리 
void history_out(char *incmd); // 명령을 히스토리에 저장한다 
void history_print(); //히스토리를 출력해준다 
char* history_cmd(int num,char *buffer);//해당명령어를 가져오기 
void command(char *inbuf);//입력받은 값에 대해서 처리해준다 
char* prompt(char *input,char *buffer);//프롬프트를 처리한다 
int signalsetup(struct sigaction *def,sigset_t *mask,void (*handler)(int));
void sigalarm(); //tiber의 구현을 위한 시그널 함수 
//스트링 처리를 위해 만든 함수들 
char* trim(char *s);
char* ltrim(char *s);
char* rtrim(char *s);
char* cut_string(int start,int end,char* str ,char* buffer);//문자열 자르기용 함수 
int str_find(char *src,char *dst);//해당 문자열의 위치를 찾기 위해 사용 
char* str_replace(char *str,char *src,char *dst,char *buffer);//글씨를 str에서 src를 dst로 바꾼다 

int pid;
extern char **environ; //환경변수를 가져오기 위한 외부변수 

int main(){
    char inbuf[MAX_CANON]; //커멘드를 입력받는 문자열 
    char prompt_str[MAX_STRING]; //프로프트가 되는 문자열 
    char tmp[MAX_STRING];
    sigset_t blockmask;
    struct sigaction defaction;


    setenv("PS","20043113 # ",1);
    if((sigaction(SIGINT,&defaction,NULL) == -1) || 
            (sigaction(SIGQUIT,&defaction,NULL) == -1) ||
            (sigaction(SIG_UNBLOCK,&defaction,NULL) == -1) ){
        perror("Failed to set signal handling for command");
    }


    for(;;){
        strcpy(prompt_str,prompt(getenv("PS"),tmp));
        if(fputs(prompt_str,stdout) == EOF)
            continue;
        if(fgets(inbuf,MAX_CANON,stdin) == NULL)
            continue;
        inbuf[strlen(inbuf)-1] = '\0';
        strcpy(inbuf,trim(inbuf));
        if(strlen(inbuf) > 1){
            if(strncmp(inbuf,QUIT_STRING,4) == 0) //exit일경우 종료 시키기 
                break;
            history_out(inbuf); //.bash_history파일에 기록한다 
            command(inbuf);
        }
    }
    unlink(".al"); //aliasing용으로 사용하던 파일을 삭제 한다 
    return 0;
}

//입력받은 커멘드를 처리 
void command(char *cmds){
    int len;
    int flag=0;
    sigset_t blockmask;
    struct sigaction defaction;

    pid_t childpid;
    len = strlen(cmds);

    if(signalsetup(&defaction,&blockmask,SIG_IGN) == -1){
        perror("Failed to set up shell signal handling");
        return;
    }

    if(sigprocmask(SIG_BLOCK,&blockmask,NULL) == -1){
        perror("Failed to block signals");
        return;
    }

    //이 명령어가 aliasing 되어있는지 확인 

    //앞에 alias가 없는 경우이면서 export가 아닌경우에만 alias에서 명령구문을 체크한다 
    FILE *in;
    int i;
    in = fopen(".al","rt");
    char buffer[MAX_STRING];
    if(in != NULL){
        char var1[MAX_STRING];
        char var2[MAX_STRING];
        for(i=1;NULL != fgets(buffer,MAX_STRING,in);i++){
            buffer[strlen(buffer)-1] = '\0';
            //=문자열의 위치 찾기 
            for(i=0;i<strlen(buffer);i++){
                if(buffer[i] == '=')
                    break;
            }
            cut_string(0,i,buffer,var1);
            cut_string(i+1,strlen(buffer),buffer,var2);
            trim(var1);
            trim(var2);

            if(strcmp(cmds ,var1) == 0)
            {
                command(var2);
                flag=1;
                break; 
            }
        }
        fclose(in);
    }

    //aliasing을 정의 하는 부분 처리 파일에 저장해두고 쓴다 
    if(strncmp(cmds,"alias",5) == 0){
        str_replace(cmds,"alias ","",cmds);
        str_replace(cmds,"\"","",cmds);
        str_replace(cmds,"\"","",cmds);
        FILE *out;
        out = fopen(".al","a");
        fprintf(out,"%s\n",cmds);
        fclose(out);
        flag=1;
    }

    //timer의 처리 
    if(strncmp(cmds,"timer",5) == 0){
        extern int errno;
        char sp[] = " ";
        int i;
        int num;
        char* cmd= NULL;
        char* token= NULL;
        int  status;
        char* arg="";
        char **chargv;
        token=strtok(cmds,sp);
        token=strtok(NULL,sp);
        num=atoi(token);
        cmd=strtok(NULL,sp);
        makeargv(cmd," \t", &chargv);
        if((pid = fork()) == 0) {
            execvp(chargv[0],chargv);
            exit(127);
        }
        signal(SIGALRM, sigalarm);
        alarm(num);
        while(wait(&status) == -1) {
            if(errno == EINTR) {
                errno = 0;
            }
            else {
                perror(cmd);
                break;
            }
        }
        //exit(status >>8);
        flag=1;
    }

    //envprint의 처리 
    if(strncmp(cmds,"envprint",8) == 0){
        char **env = environ;

        while(*env)
        {
            printf("%s\n",*env);
            env++;
        }

        flag=1;
    }


    //export에 대한 처리 
    if(strncmp(cmds,"export",6) == 0){
        char tmp[MAX_STRING];
        char var1[MAX_STRING];
        char var2[MAX_STRING];
        char buffer[MAX_STRING];
        int i=0;

        str_replace(cmds,"\"","",cmds);
        str_replace(cmds,"\"","",cmds);
        //export 잘라내기 
        cut_string(6,strlen(cmds),cmds,tmp);
        strcpy(tmp,trim(tmp));

        //=문자열의 위치 찾기 
        for(i=0;i<strlen(tmp);i++){
            if(tmp[i] == '=')
                break;
        }
        cut_string(0,i,tmp,var1);
        cut_string(i+1,strlen(tmp),tmp,var2);
        //환경변수 세팅 
        setenv(var1,var2,1);
        //테스트용 출력 
        //        printf( "$%s= \"%s\"\n", var1,getenv( var1 ) );
        flag=1;
    }


    //!!에 대한 처리 
    if(strncmp(cmds,"!!",2) == 0){ 
        char tmp2[MAX_STRING];//임시용 변수 
        history_cmd(0,tmp2);
        flag=2;
    }

    //히스토리 !숫자 형태로 실행 
    if(strncmp(cmds,"!",1) == 0 && flag != 2){
        char tmp[MAX_STRING];//임시용 변수 
        char tmp2[MAX_STRING];//임시용 변수 
        cut_string(1,strlen(cmds),cmds,tmp);
        int num=atoi(tmp);
        //printf("%s \n" , history_cmd(num,tmp2));
        history_cmd(num,tmp2);
        flag=1;
    }
    if(strncmp(cmds,"alias",5)==0){
        printf("alias\n");
        flag=1;
    }

    //히스토리의 처리 
    if(strncmp(cmds,"history",7) == 0){
        history_print();
        flag=1;
    }


    if(flag == 0){
        if(cmds[len -1] == '\n')
            cmds[ len - 1 ] = 0;
        if((childpid = fork()) == -1)
            perror("Failed to fork child");
        else if(childpid == 0){
            if((sigaction(SIGINT,&defaction,NULL) == -1) || 
                    (sigaction(SIGQUIT,&defaction,NULL) == -1) ||
                    (sigaction(SIG_UNBLOCK,&defaction,NULL) == -1) ){
                perror("Failed to set signal handling for command");
                return;
            }
            executecmd(cmds);
            return;
            //return 1;
        }else{
            wait(NULL);
        }
    }
}

//프롬프트를 처리한다 
/*
 * \H : 현재 호스트 이름을 출력
 * \t : HH:MM:SS 형식으로 24시간제 시간을 출력
 * \u : 현재 사용자 id 출력
 * \w : 현재 디렉토리 전체 경로 출력
 * 문자 : 문자가 그대로 출력
 */
char* prompt(char *input,char *buffer){
    char buf[MAX_STRING];
    strcpy(buffer,input);
    int flag=1;
    int i;
    int gethostname(char *name, size_t len);
    gethostname(buf,MAX_STRING);
    str_replace(buffer,"\\H",buf,buffer);

    //시간 구하기 
    time_t now;
    struct tm *time_now;
    now = time(NULL);
    time_now = localtime(&now);
    strftime(buf, sizeof(buf), "%H:%M:%S", time_now);
    str_replace(buffer,"\\t",buf,buffer);
    getlogin_r(buf, MAX_STRING);
    str_replace(buffer,"\\u",buf,buffer);
    getcwd(buf,MAX_STRING);
    str_replace(buffer,"\\w",buf,buffer);
    return buffer;
}

//명령어를 .bash_history파일에 저장 
void history_out(char *incmd){
    FILE *out;
    out = fopen(".bash_history","a");
    if(strlen(incmd) > 0 && strncmp(incmd,"!",1) != 0){
        fprintf(out,"%s\n",incmd);
    }
    fclose(out);
}

//히스토리를 출력해준다 
void history_print(){
    FILE *in;
    int i;
    in = fopen(".bash_history","rt");
    char buffer[MAX_STRING];
    if(in != NULL){
        for(i=1;NULL != fgets(buffer,MAX_STRING,in);i++)
            printf("%d %s",i,buffer);
        fclose(in);
    }
    printf("\n");
}

//해당 명령에 해당하는 명령어 리턴 
char* history_cmd(int num,char *buffer){
    FILE *in;
    int i;
    in = fopen(".bash_history","rt");
    char buffer_tmp[MAX_STRING];
    buffer[0]='\0';
    if(in != NULL){
        for(i=1;NULL != fgets(buffer_tmp,MAX_STRING,in);i++)
        {
            if(i==num){
                break;
            }
        }
        fclose(in);
    }
    command(buffer_tmp);
    strcpy(buffer,buffer_tmp);
    return buffer;
}

//에러처리를 한다 
static void perror_exit(char *s){
    perror(s);
    exit(1);
}

//명령어의 실행을 관장한다 
void executecmd(char *cmds){
    int child;
    int count;
    int fds[2];
    int i;
    int flag =0;//내부 명령어 여부를 나타내는 플레그 
    char **pipelist;
    //flag가 0일때는 내부명령어가 아니므로 명령어를 처리 
    if(flag == 0){
        count = makeargv(cmds, "|",&pipelist);
        if(count <= 0){
            fprintf(stderr,"Failed to find any commands\n");
            exit(1);
        }
        for(i=0;i<count -1;i++){
            if(pipe(fds) == -1)
                perror_exit("Failed to create pipes");
            else if((child = fork()) == -1)
                perror_exit("Failed to create process to run command");
            else if(child){
                if(dup2(fds[1],STDOUT_FILENO) == -1)
                    perror_exit("Failed to connect pipeline");
                if(close(fds[0]) || close(fds[1]))
                    perror_exit("Failed to close needed files");
                executeredirect(pipelist[i],i==0,0);
                exit(1);
            }
            if(dup2(fds[0],STDIN_FILENO) == -1)
                perror_exit("Failed to connect last component");
            if(close(fds[0]) || close(fds[1]))
                perror_exit("Failed to do final close");
        }
        executeredirect(pipelist[i],i==0,1);
    }
    exit(1);
}


//리다이렉션의 총체적인 처리를 한다 
void executeredirect(char *s, int in, int out){
    char **chargv;
    char *pin;
    char *pout;

    if(in && ((pin = strchr(s,'<')) != NULL) && out && ((pout = strchr(s,'>')) != NULL) && (pin > pout)){
        if(parseandredirectin(s) == -1){
            perror("Failed to redirect input");
            return ;
        }
        in = 0;
    }


    if(out && (parseandredirectadd(s) == -1)){
        perror("Failed to redirect add");
    }else if(out && (parseandredirectout(s) == -1))
        perror("Failed to redirect output");
    else if(in && (parseandredirectin(s) == -1))
        perror("Failed to redirect input");
    else if(makeargv(s," \t", &chargv) <=0)
        fprintf(stderr,"Failed to parse command line \n");
    else{
        execvp(chargv[0],chargv);
        perror("Failed to execute command");
    }
    exit(1);
}



//알람이 울리면 SIGKILL함수를 호출하도록 했다 
void sigalarm(sig)
    int sig;
{
    kill(pid,SIGKILL);
}

//<를 처리한다 
int parseandredirectin(char *cmd) {
    int error;
    int infd;
    char *infile;
    if((infile = strchr(cmd,'<')) == NULL)
        return 0;
    *infile = 0;
    infile = strtok(infile+1," \t");
    if(infile == NULL)
        return 0;
    if((infd = open(infile, O_RDONLY)) == -1)
        return -1;
    if(dup2(infd,STDIN_FILENO) == -1){
        error = errno;
        close(infd);
        errno = error;
        return -1;
    }
    return close(infd);
}

// >를 처리한다 
int parseandredirectout(char *cmd){
    int error;
    int outfd;
    char *outfile;
    if((outfile = strchr(cmd,'>')) == NULL)
        return 0;
    *outfile = 0;
    outfile = strtok(outfile +1," \t");
    if(outfile == NULL)
        return 0;
    if((outfd = open(outfile,FFLAG,FMODE)) == -1)
        return -1;
    if(dup2(outfd, STDOUT_FILENO) == -1) {
        error = errno;
        close(outfd);
        errno = error;
        return -1;
    }
    return close(outfd);
}

// >>를 처리한다 
int parseandredirectadd(char *cmd){
    int error;
    int outfd;
    char *outfile;
    if((outfile = strstr(cmd,">>")) == NULL)
        return 0;
    *outfile = 0;
    outfile = strtok(outfile +2," \t");
    if(outfile == NULL)
        return 0;
    if((outfd = open(outfile,FFLAG_ADD,FMODE)) == -1)
        return -1;

    if(dup2(outfd, STDOUT_FILENO) == -1) {
        error = errno;
        close(outfd);
        errno = error;
        return -1;
    }
    return close(outfd);

}
//인자를 처리한다 
int makeargv(const char *s, const char *delimiters, char ***argvp){
    int error;
    int i;
    int numtokens;
    const char *snew;
    char *t;

    if((s == NULL) || (delimiters == NULL) || (argvp == NULL)){
        errno = EINVAL;
        return -1;
    }
    *argvp = NULL;
    snew = s+strspn(s,delimiters);
    if((t = malloc(strlen(snew) + 1)) == NULL)
        return -1;
    strcpy(t,snew);
    numtokens = 0;
    if(strtok(t,delimiters) != NULL)
        for(numtokens = 1; strtok(NULL,delimiters) != NULL;numtokens++);

    if((*argvp = malloc((numtokens +1)*sizeof(char *))) == NULL){
        error = errno;
        free(t);
        errno = error;
        return -1;
    }
    if(numtokens == 0)
        free(t);
    else{
        strcpy(t,snew);
        **argvp=strtok(t,delimiters);
        for(i=1;i<numtokens;i++)
            *((*argvp) + i) = strtok(NULL,delimiters);
    }
    *((*argvp) + numtokens) = NULL;
    return numtokens;
}

int signalsetup(struct sigaction *def,sigset_t *mask,void (*handler)(int)){
    struct sigaction catch;
    catch.sa_handler = handler;
    def->sa_handler = SIG_DFL;
    catch.sa_flags = 0;
    def->sa_flags = 0;
    if((sigemptyset(&(def->sa_mask)) == -1) || 
            (sigemptyset(&(catch.sa_mask)) == -1) || 
            (sigaddset(&(catch.sa_mask),SIGINT) == -1) || 
            (sigaddset(&(catch.sa_mask),SIGQUIT) == -1) ||
            (sigaction(SIGINT,&catch,NULL) == -1) ||
            (sigaction(SIGQUIT,&catch,NULL) == -1) ||
            (sigemptyset(mask) == -1) || 
            (sigaddset(mask,SIGINT) == -1) ||
            (sigaddset(mask,SIGQUIT) == -1))
        return 0;
}


//문자열 자르기용 함수 
char* cut_string(int start,int end,char* str ,char* buffer){
    snprintf(buffer,end+1,"%s",str+start);
    return buffer;
}


//오른쪽 공백처리 
char* rtrim(char* s){
    char t[MAX_STRING];
    char *end;
    strcpy(t,s);
    end = t + strlen(t) -1;
    while(end != t && isspace(*end))
        end--;
    *(end + 1) = '\0';
    s = t;
    return s;
}

//왼쪽 공백처리 
char* ltrim(char *s){
    char* begin;
    begin = s;
    while(*begin != '\0'){
        if(isspace(*begin))
            begin++;
        else{
            s=begin;
            break;
        }
    }
    return s;
}

//양쪽 공백처리 
char* trim(char *s){
    return rtrim(ltrim(s));
}



//문자열의 해당위치를 찾아주는 함수 
int str_find(char *src,char *dst){
    int i;
    for(i=0;i<strlen(src);i++){
        if(strncmp(src+i,dst,strlen(dst)) == 0){
            break;
        }
    }
    return i;
}



//글짜 바꾸기 함수 
char* str_replace(char *str,char *src,char *dst,char *buffer){
    char cut[128];
    char buf[128]="";
    char buf2[128];
    int l = str_find(str,src);
    if(l != strlen(str)){
        cut_string(0,l,str,cut);
        strncat(buf,cut,128);
        strncat(buf,dst,128);
        strncat(buf,cut_string(l+strlen(src),strlen(str),str,buf2),128);
        strcpy(buffer,buf);
    }
    return buffer;
}
